package org.andromda.cartridges.jsf.metafacades;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.andromda.cartridges.jsf.JSFGlobals;
import org.andromda.cartridges.jsf.JSFProfile;
import org.andromda.cartridges.jsf.JSFUtils;
import org.andromda.metafacades.uml.AttributeFacade;
import org.andromda.metafacades.uml.ClassifierFacade;
import org.andromda.metafacades.uml.EventFacade;
import org.andromda.metafacades.uml.FrontEndAction;
import org.andromda.metafacades.uml.FrontEndActivityGraph;
import org.andromda.metafacades.uml.FrontEndForward;
import org.andromda.metafacades.uml.FrontEndParameter;
import org.andromda.metafacades.uml.FrontEndView;
import org.andromda.metafacades.uml.ModelElementFacade;
import org.andromda.metafacades.uml.TransitionFacade;
import org.andromda.metafacades.uml.UseCaseFacade;
import org.andromda.utils.StringUtilsHelper;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;

/**
 * MetafacadeLogic implementation for
 * org.andromda.cartridges.jsf.metafacades.JSFParameter.
 * 
 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter
 */
public class JSFParameterLogicImpl extends JSFParameterLogic {
	private static final long serialVersionUID = 34L;

	/**
	 * @param metaObject
	 * @param context
	 */
	public JSFParameterLogicImpl(Object metaObject, String context) {
		super(metaObject, context);
	}

	/**
	 * Overridden to make sure it's not an inputTable.
	 * 
	 * @see org.andromda.metafacades.uml.FrontEndParameter#isTable()
	 */
	public boolean isTable() {
		return (super.isTable() || this.isPageableTable())
				&& !this.isSelectable() && !this.isInputTable()
				&& !this.isInputHidden();
	}

	/**
	 * @return isPageableTable
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isPageableTable()
	 */
	protected boolean handleIsPageableTable() {
		final Object value = this
				.findTaggedValue(JSFProfile.TAGGEDVALUE_TABLE_PAGEABLE);
		return Boolean.valueOf(ObjectUtils.toString(value)).booleanValue();
	}

	/**
	 * @return messageKey
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getMessageKey()
	 */
	protected String handleGetMessageKey() {
		final StringBuilder messageKey = new StringBuilder();

		if (!this.isNormalizeMessages()) {
			if (this.isActionParameter()) {
				final JSFAction action = (JSFAction) this.getAction();
				if (action != null) {
					messageKey.append(action.getMessageKey());
					messageKey.append('.');
				}
			} else {
				final JSFView view = (JSFView) this.getView();
				if (view != null) {
					messageKey.append(view.getMessageKey());
					messageKey.append('.');
				}
			}
			messageKey.append("param.");
		}

		messageKey.append(StringUtilsHelper.toResourceMessageKey(super
				.getName()));
		return messageKey.toString();
	}

	/**
	 * @return getMessageKey() + '.' +
	 *         JSFGlobals.DOCUMENTATION_MESSAGE_KEY_SUFFIX
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getDocumentationKey()
	 */
	protected String handleGetDocumentationKey() {
		return getMessageKey() + '.'
				+ JSFGlobals.DOCUMENTATION_MESSAGE_KEY_SUFFIX;
	}

	/**
	 * @return documentationValue
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getDocumentationValue()
	 */
	protected String handleGetDocumentationValue() {
		final String value = StringUtilsHelper.toResourceMessage(this
				.getDocumentation("", 64, false));
		return value == null ? "" : value;
	}

	/**
	 * Indicates whether or not we should normalize messages.
	 * 
	 * @return true/false
	 */
	private boolean isNormalizeMessages() {
		final String normalizeMessages = (String) getConfiguredProperty(JSFGlobals.NORMALIZE_MESSAGES);
		return Boolean.valueOf(normalizeMessages).booleanValue();
	}

	/**
	 * @return messageValue
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getMessageValue()
	 */
	protected String handleGetMessageValue() {
		return StringUtilsHelper.toPhrase(super.getName()); // the actual name
															// is used for
															// displaying
	}

	/**
	 * @param columnName
	 * @return tableColumnMessageKey
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getTableColumnMessageKey(String)
	 */
	protected String handleGetTableColumnMessageKey(final String columnName) {
		StringBuilder messageKey = new StringBuilder();
		if (!this.isNormalizeMessages()) {
			final JSFView view = (JSFView) this.getView();
			if (view != null) {
				messageKey.append(this.getMessageKey());
				messageKey.append('.');
			}
		}
		messageKey.append(StringUtilsHelper.toResourceMessageKey(columnName));
		return messageKey.toString();
	}

	/**
	 * @param columnName
	 * @return StringUtilsHelper.toPhrase(columnName)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getTableColumnMessageValue(String)
	 */
	protected String handleGetTableColumnMessageValue(final String columnName) {
		return StringUtilsHelper.toPhrase(columnName);
	}

	/**
	 * @return getTableActions(true)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getTableHyperlinkActions()
	 */
	protected List<JSFAction> handleGetTableHyperlinkActions() {
		return this.getTableActions(true);
	}

	/**
	 * If this is a table this method returns all those actions that are
	 * declared to work on this table.
	 * 
	 * @param hyperlink
	 *            denotes on which type of actions to filter
	 */
	private List<JSFAction> getTableActions(boolean hyperlink) {
		final Set<JSFAction> actions = new LinkedHashSet<JSFAction>();
		final String name = StringUtils.trimToNull(getName());
		if (name != null && isTable()) {
			final JSFView view = (JSFView) this.getView();

			final Collection<UseCaseFacade> allUseCases = getModel()
					.getAllUseCases();
			for (final UseCaseFacade useCase : allUseCases) {
				if (useCase instanceof JSFUseCase) {
					final FrontEndActivityGraph graph = ((JSFUseCase) useCase)
							.getActivityGraph();
					if (graph != null) {
						final Collection<TransitionFacade> transitions = graph
								.getTransitions();
						for (final TransitionFacade transition : transitions) {
							if (transition.getSource().equals(view)
									&& transition instanceof JSFAction) {
								final JSFAction action = (JSFAction) transition;
								if (action.isTableLink()
										&& name.equals(action
												.getTableLinkName())) {
									if (hyperlink == action.isHyperlink()) {
										actions.add(action);
									}
								}
							}
						}
					}
				}
			}
		}
		return new ArrayList<JSFAction>(actions);
	}

	/**
	 * @return getTableActions(false)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getTableFormActions()
	 */
	protected List<JSFAction> handleGetTableFormActions() {
		return this.getTableActions(false);
	}

	/**
	 * @see org.andromda.metafacades.uml.FrontEndParameter#getTableColumns()
	 */
	// TODO tableColumns can be either String or JSFParameter. Should use a
	// single return type in Collection.
	public Collection getTableColumns() {
		final Collection tableColumns = super.getTableColumns();
		if (tableColumns.isEmpty()) {
			// try to preserve the order of the elements encountered
			// final Map<String, JSFParameter> tableColumnsMap = new
			// LinkedHashMap<String, JSFParameter>();
			final Map tableColumnsMap = new LinkedHashMap();

			// order is important
			final List<JSFAction> actions = new ArrayList<JSFAction>();

			// all table actions need the exact same parameters, just not always
			// all of them
			actions.addAll(this.getTableFormActions());

			// if there are any actions that are hyperlinks then their
			// parameters get priority
			// the user should not have modeled it that way (constraints will
			// warn him/her)
			actions.addAll(this.getTableHyperlinkActions());

			for (final JSFAction action : actions) {
				for (final FrontEndParameter actionParameter : action
						.getParameters()) {
					if (actionParameter instanceof JSFParameter) {
						final JSFParameter parameter = (JSFParameter) actionParameter;
						final String parameterName = parameter.getName();
						if (parameterName != null) {
							// never overwrite column specific table links
							// the hyperlink table links working on a real
							// column get priority
							final Object existingObject = tableColumnsMap
									.get(parameterName);
							if (existingObject instanceof JSFParameter) {
								if (action.isHyperlink()
										&& parameterName.equals(action
												.getTableLinkColumnName())) {
									tableColumnsMap.put(parameterName,
											parameter);
								}
							}
						}
					}
				}
			}

			// for any missing parameters we just add the name of the column
			for (final String columnName : this.getTableColumnNames()) {
				if (!tableColumnsMap.containsKey(columnName)) {
					tableColumnsMap.put(columnName, columnName);
				}
			}

			// return everything in the same order as it has been modeled (using
			// the table tagged value)
			for (final String columnObject : this.getTableColumnNames()) {
				tableColumns.add(tableColumnsMap.get(columnObject));
			}
		}
		return tableColumns;
	}

	/**
	 * @return the default date format pattern as defined using the configured
	 *         property
	 */
	private String getDefaultDateFormat() {
		return (String) this
				.getConfiguredProperty(JSFGlobals.PROPERTY_DEFAULT_DATEFORMAT);
	}

	/**
	 * @return format
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getFormat()
	 */
	protected String handleGetFormat() {
		return JSFUtils.getFormat((ModelElementFacade) this.THIS(),
				this.getType(), this.getDefaultDateFormat(),
				this.getDefaultTimeFormat());
	}

	/**
	 * @return the default time format pattern as defined using the configured
	 *         property
	 */
	private String getDefaultTimeFormat() {
		return (String) this
				.getConfiguredProperty(JSFGlobals.PROPERTY_DEFAULT_TIMEFORMAT);
	}

	/**
	 * @return JSFUtils.isStrictDateFormat((ModelElementFacade)this.THIS())
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isStrictDateFormat()
	 */
	protected boolean handleIsStrictDateFormat() {
		return JSFUtils.isStrictDateFormat((ModelElementFacade) this.THIS());
	}

	/**
	 * @return dateFormatter
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getDateFormatter()
	 */
	protected String handleGetDateFormatter() {
		final ClassifierFacade type = this.getType();
		return type != null && type.isDateType() ? this.getName()
				+ "DateFormatter" : null;
	}

	/**
	 * @return timeFormatter
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getTimeFormatter()
	 */
	protected String handleGetTimeFormatter() {
		final ClassifierFacade type = this.getType();
		return type != null && type.isTimeType() ? this.getName()
				+ "TimeFormatter" : null;
	}

	/**
	 * Gets the current value of the specified input type (or an empty string if
	 * one isn't specified).
	 * 
	 * @return the input type name.
	 */
	private String getInputType() {
		return ObjectUtils.toString(
				this.findTaggedValue(JSFProfile.TAGGEDVALUE_INPUT_TYPE)).trim();
	}

	/**
	 * Indicates whether or not this parameter is of the given input type.
	 * 
	 * @param inputType
	 *            the name of the input type to check for.
	 * @return true/false
	 */
	private boolean isInputType(final String inputType) {
		return inputType.equalsIgnoreCase(this.getInputType());
	}

	/**
	 * @return isInputType(JSFGlobals.INPUT_TEXTAREA)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputTextarea()
	 */
	protected boolean handleIsInputTextarea() {
		return this.isInputType(JSFGlobals.INPUT_TEXTAREA);
	}

	/**
	 * @return isInputType(JSFGlobals.INPUT_SELECT)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputSelect()
	 */
	protected boolean handleIsInputSelect() {
		return this.isInputType(JSFGlobals.INPUT_SELECT);
	}

	/**
	 * @return isInputType(JSFGlobals.INPUT_PASSWORD)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputSecret()
	 */
	protected boolean handleIsInputSecret() {
		return this.isInputType(JSFGlobals.INPUT_PASSWORD);
	}

	/**
	 * @return isInputType(JSFGlobals.INPUT_HIDDEN)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputHidden()
	 */
	protected boolean handleIsInputHidden() {
		return this.isInputType(JSFGlobals.INPUT_HIDDEN);
	}

	/**
	 * @return isInputType(JSFGlobals.PLAIN_TEXT)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isPlaintext()
	 */
	protected boolean handleIsPlaintext() {
		return this.isInputType(JSFGlobals.PLAIN_TEXT);
	}

	/**
	 * @return isInputTable
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputTable()
	 */
	protected boolean handleIsInputTable() {
		return this.getInputTableIdentifierColumns().length() > 0
				|| this.isInputType(JSFGlobals.INPUT_TABLE);
	}

	/**
	 * @return isInputType(JSFGlobals.INPUT_RADIO)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputRadio()
	 */
	protected boolean handleIsInputRadio() {
		return this.isInputType(JSFGlobals.INPUT_RADIO);
	}

	/**
	 * @return isInputType(JSFGlobals.INPUT_TEXT)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputText()
	 */
	protected boolean handleIsInputText() {
		return this.isInputType(JSFGlobals.INPUT_TEXT);
	}

	/**
	 * @return isInputType(JSFGlobals.INPUT_MULTIBOX)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputMultibox()
	 */
	protected boolean handleIsInputMultibox() {
		return this.isInputType(JSFGlobals.INPUT_MULTIBOX);
	}

	/**
	 * @return isInputCheckbox
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputCheckbox()
	 */
	protected boolean handleIsInputCheckbox() {
		boolean checkbox = this.isInputType(JSFGlobals.INPUT_CHECKBOX);
		if (!checkbox && this.getInputType().length() == 0) {
			final ClassifierFacade type = this.getType();
			checkbox = type != null ? type.isBooleanType() : false;
		}
		return checkbox;
	}

	/**
	 * @return isInputFile
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isInputFile()
	 */
	protected boolean handleIsInputFile() {
		boolean file = false;
		ClassifierFacade type = getType();
		if (type != null) {
			file = type.isFileType();
		}
		return file;
	}

	/**
	 * @return backingListName
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getBackingListName()
	 */
	protected String handleGetBackingListName() {
		return ObjectUtils.toString(
				this.getConfiguredProperty(JSFGlobals.BACKING_LIST_PATTERN))
				.replaceAll("\\{0\\}", this.getName());
	}

	/**
	 * @return backingValueName
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getBackingValueName()
	 */
	protected String handleGetBackingValueName() {
		return ObjectUtils.toString(
				this.getConfiguredProperty(JSFGlobals.BACKING_VALUE_PATTERN))
				.replaceAll("\\{0\\}", this.getName());
	}

	/**
	 * @return valueListName
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getValueListName()
	 */
	protected String handleGetValueListName() {
		return ObjectUtils.toString(
				this.getConfiguredProperty(JSFGlobals.VALUE_LIST_PATTERN))
				.replaceAll("\\{0\\}", this.getName());
	}

	/**
	 * @return labelListName
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getLabelListName()
	 */
	protected String handleGetLabelListName() {
		return ObjectUtils.toString(
				this.getConfiguredProperty(JSFGlobals.LABEL_LIST_PATTERN))
				.replaceAll("\\{0\\}", this.getName());
	}

	/**
	 * @return isSelectable
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isSelectable()
	 */
	protected boolean handleIsSelectable() {
		boolean selectable = false;
		if (this.isActionParameter()) {
			selectable = this.isInputMultibox() || this.isInputSelect()
					|| this.isInputRadio();
			final ClassifierFacade type = this.getType();

			if (!selectable && type != null) {
				final String name = this.getName();
				final String typeName = type.getFullyQualifiedName();

				// - if the parameter is not selectable but on a targetting page
				// it IS selectable we must
				// allow the user to set the backing list too
				final Collection<FrontEndView> views = this.getAction()
						.getTargetViews();
				for (final Iterator<FrontEndView> iterator = views.iterator(); iterator
						.hasNext() && !selectable;) {
					final Collection<FrontEndParameter> parameters = iterator
							.next().getAllActionParameters();
					for (final Iterator<FrontEndParameter> parameterIterator = parameters
							.iterator(); parameterIterator.hasNext()
							&& !selectable;) {
						final Object object = parameterIterator.next();
						if (object instanceof JSFParameter) {
							final JSFParameter parameter = (JSFParameter) object;
							final String parameterName = parameter.getName();
							final ClassifierFacade parameterType = parameter
									.getType();
							if (parameterType != null) {
								final String parameterTypeName = parameterType
										.getFullyQualifiedName();
								if (name.equals(parameterName)
										&& typeName.equals(parameterTypeName)) {
									selectable = parameter.isInputMultibox()
											|| parameter.isInputSelect()
											|| parameter.isInputRadio();
								}
							}
						}
					}
				}
			}
		} else if (this.isControllerOperationArgument()) {
			final String name = this.getName();
			final Collection actions = this.getControllerOperation()
					.getDeferringActions();
			for (final Iterator actionIterator = actions.iterator(); actionIterator
					.hasNext();) {
				final JSFAction action = (JSFAction) actionIterator.next();
				final Collection<FrontEndParameter> formFields = action
						.getFormFields();
				for (final Iterator<FrontEndParameter> fieldIterator = formFields
						.iterator(); fieldIterator.hasNext() && !selectable;) {
					final Object object = fieldIterator.next();
					if (object instanceof JSFParameter) {
						final JSFParameter parameter = (JSFParameter) object;
						if (!parameter.equals(this)) {
							if (name.equals(parameter.getName())) {
								selectable = parameter.isSelectable();
							}
						}
					}
				}
			}
		}
		return selectable;
	}

	/**
	 * Stores the initial value of each type.
	 */
	private final Map<String, String> initialValues = new HashMap<String, String>();

	/**
	 * @return constructDummyArray()
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getValueListDummyValue()
	 */
	protected String handleGetValueListDummyValue() {
		return this.constructDummyArray();
	}

	/**
	 * @return dummyValue
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getDummyValue()
	 */
	protected String handleGetDummyValue() {
		final ClassifierFacade type = this.getType();
		final String typeName = type != null ? type.getFullyQualifiedName()
				: "";
		String initialValue = null;
		if (type != null) {
			if (type.isSetType()) {
				initialValue = "new java.util.LinkedHashSet(java.util.Arrays.asList("
						+ this.constructDummyArray() + "))";
			} else if (type.isCollectionType()) {
				initialValue = "java.util.Arrays.asList("
						+ this.constructDummyArray() + ")";
			} else if (type.isArrayType()) {
				initialValue = this.constructDummyArray();
			}
			final String name = this.getName() != null ? this.getName() : "";
			if (this.initialValues.isEmpty()) {
				initialValues.put(boolean.class.getName(), "false");
				initialValues.put(int.class.getName(),
						"(int)" + name.hashCode());
				initialValues.put(long.class.getName(),
						"(long)" + name.hashCode());
				initialValues.put(short.class.getName(),
						"(short)" + name.hashCode());
				initialValues.put(byte.class.getName(),
						"(byte)" + name.hashCode());
				initialValues.put(float.class.getName(),
						"(float)" + name.hashCode());
				initialValues.put(double.class.getName(),
						"(double)" + name.hashCode());
				initialValues.put(char.class.getName(),
						"(char)" + name.hashCode());

				initialValues.put(String.class.getName(), "\"" + name + "-test"
						+ "\"");
				initialValues.put(java.util.Date.class.getName(),
						"new java.util.Date()");
				initialValues.put(java.sql.Date.class.getName(),
						"new java.util.Date()");
				initialValues.put(java.sql.Timestamp.class.getName(),
						"new java.util.Date()");

				initialValues.put(Integer.class.getName(), "new Integer((int)"
						+ name.hashCode() + ")");
				initialValues.put(Boolean.class.getName(), "Boolean.FALSE");
				initialValues.put(Long.class.getName(), "new Long((long)"
						+ name.hashCode() + ")");
				initialValues.put(Character.class.getName(),
						"new Character(char)" + name.hashCode() + ")");
				initialValues.put(Float.class.getName(), "new Float((float)"
						+ name.hashCode() / hashCode() + ")");
				initialValues.put(Double.class.getName(), "new Double((double)"
						+ name.hashCode() / hashCode() + ")");
				initialValues.put(Short.class.getName(), "new Short((short)"
						+ name.hashCode() + ")");
				initialValues.put(Byte.class.getName(), "new Byte((byte)"
						+ name.hashCode() + ")");
			}
			if (initialValue == null) {
				initialValue = this.initialValues.get(typeName);
			}
		}
		if (initialValue == null) {
			initialValue = "null";
		}
		return initialValue;
	}

	/**
	 * Constructs a string representing an array initialization in Java.
	 * 
	 * @return A String representing Java code for the initialization of an
	 *         array.
	 */
	private String constructDummyArray() {
		return JSFUtils.constructDummyArrayDeclaration(this.getName(),
				JSFGlobals.DUMMY_ARRAY_COUNT);
	}

	/**
	 * @return getName() + "SortColumn"
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getTableSortColumnProperty()
	 */
	protected String handleGetTableSortColumnProperty() {
		return this.getName() + "SortColumn";
	}

	/**
	 * @return getName() + "SortAscending"
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getTableSortAscendingProperty()
	 */
	protected String handleGetTableSortAscendingProperty() {
		return this.getName() + "SortAscending";
	}

	/**
	 * @return getName() + "Set"
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getFormAttributeSetProperty()
	 */
	protected String handleGetFormAttributeSetProperty() {
		return this.getName() + "Set";
	}

	// TODO remove after 3.4 release
	/**
	 * Hack to keep the compatibility with Andromda 3.4-SNAPSHOT
	 */
	/**
	 * @see org.andromda.metafacades.uml.FrontEndParameter#getView()
	 */
	public FrontEndView getView() {
		Object view = null;
		final EventFacade event = this.getEvent();
		if (event != null) {
			final TransitionFacade transition = event.getTransition();
			if (transition instanceof JSFActionLogicImpl) {
				final JSFActionLogicImpl action = (JSFActionLogicImpl) transition;
				view = action.getInput();
			} else if (transition instanceof FrontEndForward) {
				final FrontEndForward forward = (FrontEndForward) transition;
				if (forward.isEnteringView()) {
					view = forward.getTarget();
				}
			}
		}
		return (FrontEndView) view;
	}

	/**
	 * @return validationRequired
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isValidationRequired()
	 */
	protected boolean handleIsValidationRequired() {
		boolean required = !this.getValidatorTypes().isEmpty();
		if (!required) {
			// - look for any attributes
			for (final Iterator<JSFAttribute> iterator = this.getAttributes()
					.iterator(); iterator.hasNext();) {
				required = !iterator.next().getValidatorTypes().isEmpty();
				if (required) {
					break;
				}
			}

			// - look for any table columns
			if (!required) {
				for (final Iterator iterator = this.getTableColumns()
						.iterator(); iterator.hasNext();) {
					final Object object = iterator.next();
					if (object instanceof JSFAttribute) {
						final JSFAttribute attribute = (JSFAttribute) object;
						required = !attribute.getValidatorTypes().isEmpty();
						if (required) {
							break;
						}
					}
				}
			}
		}
		return required;
	}

	/**
	 * @return validatorTypes
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getValidatorTypes()
	 */
	protected Collection handleGetValidatorTypes() {
		return JSFUtils.getValidatorTypes((ModelElementFacade) this.THIS(),
				this.getType());
	}

	/**
	 * @return JSFUtils.getValidWhen(this)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getValidWhen()
	 */
	protected String handleGetValidWhen() {
		return JSFUtils.getValidWhen(this);
	}

	/**
	 * Overridden to have the same behavior as bpm4struts.
	 * 
	 * @see org.andromda.metafacades.uml.ParameterFacade#isRequired()
	 */
	public boolean isRequired() {
		if ("org.omg.uml.foundation.core".equals(metaObject.getClass()
				.getPackage().getName())) {
			// if uml 1.4, keep the old behavior (like bpm4struts)
			final Object value = this
					.findTaggedValue(JSFProfile.TAGGEDVALUE_INPUT_REQUIRED);
			return Boolean.valueOf(ObjectUtils.toString(value)).booleanValue();
		} else {
			// if >= uml 2, default behavior
			return super.isRequired();
		}
	}

	/**
	 * @return JSFUtils.isReadOnly(this)
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isReadOnly()
	 */
	protected boolean handleIsReadOnly() {
		return JSFUtils.isReadOnly(this);
	}

	/**
	 * @param validatorType
	 * @return validatorArgs
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getValidatorArgs(String)
	 */
	protected Collection handleGetValidatorArgs(final String validatorType) {
		return JSFUtils.getValidatorArgs((ModelElementFacade) this.THIS(),
				validatorType);
	}

	/**
	 * @return validatorVars
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getValidatorVars()
	 */
	protected Collection handleGetValidatorVars() {
		return JSFUtils.getValidatorVars((ModelElementFacade) this.THIS(),
				this.getType(), null);
	}

	/**
	 * @return reset
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isReset()
	 */
	protected boolean handleIsReset() {
		boolean reset = Boolean.valueOf(
				ObjectUtils.toString(this
						.findTaggedValue(JSFProfile.TAGGEDVALUE_INPUT_RESET)))
				.booleanValue();
		if (!reset) {
			final JSFAction action = (JSFAction) this.getAction();
			reset = action != null && action.isFormReset();
		}
		return reset;
	}

	/**
	 * @return complex
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isComplex()
	 */
	protected boolean handleIsComplex() {
		boolean complex = false;
		final ClassifierFacade type = this.getType();
		if (type != null) {
			complex = !type.getAttributes().isEmpty();
			if (!complex) {
				complex = !type.getAssociationEnds().isEmpty();
			}
		}
		return complex;
	}

	/**
	 * @return attributes
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getAttributes()
	 */
	protected Collection<AttributeFacade> handleGetAttributes() {
		Collection<AttributeFacade> attributes = null;
		ClassifierFacade type = this.getType();
		if (type != null) {
			if (type.isArrayType()) {
				type = type.getNonArray();
			}
			if (type != null) {
				attributes = type.getAttributes(true);
			}
		}
		return attributes == null ? new ArrayList<AttributeFacade>()
				: attributes;
	}

	/**
	 * @return navigableAssociationEnds
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getNavigableAssociationEnds()
	 */
	protected Collection<ClassifierFacade> handleGetNavigableAssociationEnds() {
		Collection<ClassifierFacade> associationEnds = null;
		ClassifierFacade type = this.getType();
		if (type != null) {
			if (type.isArrayType()) {
				type = type.getNonArray();
			}
			if (type != null) {
				associationEnds = type.getNavigableConnectingEnds();
			}
		}
		return associationEnds == null ? new ArrayList<ClassifierFacade>()
				: associationEnds;
	}

	/**
	 * @return isEqualValidator
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isEqualValidator()
	 */
	protected boolean handleIsEqualValidator() {
		final String equal = JSFUtils
				.getEqual((ModelElementFacade) this.THIS());
		return equal != null && equal.trim().length() > 0;
	}

	/**
	 * @return isBackingValueRequired
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#isEqualValidator()
	 */
	protected boolean handleIsBackingValueRequired() {
		boolean required = false;
		if (this.isActionParameter()) {
			required = this.isInputTable();
			final ClassifierFacade type = this.getType();

			if (!required && type != null) {
				final String name = this.getName();
				final String typeName = type.getFullyQualifiedName();

				// - if the backing value is not required for this parameter but
				// on
				// a targeting page it IS selectable we must allow the user to
				// set the backing value as well
				final Collection<FrontEndView> views = this.getAction()
						.getTargetViews();
				for (final Iterator<FrontEndView> iterator = views.iterator(); iterator
						.hasNext() && !required;) {
					final Collection<FrontEndParameter> parameters = iterator
							.next().getAllActionParameters();
					for (final Iterator<FrontEndParameter> parameterIterator = parameters
							.iterator(); parameterIterator.hasNext()
							&& !required;) {
						final FrontEndParameter object = parameterIterator
								.next();
						if (object instanceof JSFParameter) {
							final JSFParameter parameter = (JSFParameter) object;
							final String parameterName = parameter.getName();
							final ClassifierFacade parameterType = parameter
									.getType();
							if (parameterType != null) {
								final String parameterTypeName = parameterType
										.getFullyQualifiedName();
								if (name.equals(parameterName)
										&& typeName.equals(parameterTypeName)) {
									required = parameter.isInputTable();
								}
							}
						}
					}
				}
			}
		} else if (this.isControllerOperationArgument()) {
			final String name = this.getName();
			final Collection<FrontEndAction> actions = this
					.getControllerOperation().getDeferringActions();
			for (final Iterator<FrontEndAction> actionIterator = actions
					.iterator(); actionIterator.hasNext();) {
				final JSFAction action = (JSFAction) actionIterator.next();
				final Collection<FrontEndParameter> formFields = action
						.getFormFields();
				for (final Iterator<FrontEndParameter> fieldIterator = formFields
						.iterator(); fieldIterator.hasNext() && !required;) {
					final Object object = fieldIterator.next();
					if (object instanceof JSFParameter) {
						final JSFParameter parameter = (JSFParameter) object;
						if (!parameter.equals(this)) {
							if (name.equals(parameter.getName())) {
								required = parameter.isBackingValueRequired();
							}
						}
					}
				}
			}
		}
		return required;
	}

	/**
	 * @return 
	 *         findTaggedValue(JSFProfile.TAGGEDVALUE_INPUT_TABLE_IDENTIFIER_COLUMNS
	 *         )
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getInputTableIdentifierColumns()
	 */
	protected String handleGetInputTableIdentifierColumns() {
		return ObjectUtils
				.toString(
						this.findTaggedValue(JSFProfile.TAGGEDVALUE_INPUT_TABLE_IDENTIFIER_COLUMNS))
				.trim();
	}

	/**
	 * @param columnName
	 * @return tableColumnActions
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getTableColumnActions(String)
	 */
	protected List<JSFAction> handleGetTableColumnActions(
			final String columnName) {
		final List<JSFAction> columnActions = new ArrayList<JSFAction>();

		if (columnName != null) {
			final Set<JSFAction> actions = new LinkedHashSet<JSFAction>(
					this.getTableHyperlinkActions());
			actions.addAll(this.getTableFormActions());
			for (final JSFAction action : actions) {
				if (columnName.equals(action.getTableLinkColumnName())) {
					columnActions.add(action);
				}
			}
		}

		return columnActions;
	}

	/**
	 * @return maxLength
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getMaxLength()
	 */
	protected String handleGetMaxLength() {
		final Collection<Collection> vars = getValidatorVars();
		if (vars == null) {
			return null;
		}
		for (Iterator<Collection> it = vars.iterator(); it.hasNext();) {
			final Object[] values = (it.next()).toArray();
			if ("maxlength".equals(values[0])) {
				return values[1].toString();
			}
		}
		return null;
	}

	// to be used in the range validator: "range - 1000" or "range 20 -".
	/** - */
	static final String UNDEFINED_BOUND = "-";
	/** javax.validation.constraints.NotNull */
	static final String AN_REQUIRED = "@javax.validation.constraints.NotNull";
	/** org.hibernate.validator.constraints.URL */
	static final String AN_URL = "@org.hibernate.validator.constraints.URL";
	/** org.apache.myfaces.extensions.validator.baseval.annotation.LongRange */
	static final String AN_LONG_RANGE = "@org.apache.myfaces.extensions.validator.baseval.annotation.LongRange";
	/** org.apache.myfaces.extensions.validator.baseval.annotation.DoubleRange */
	static final String AN_DOUBLE_RANGE = "@org.apache.myfaces.extensions.validator.baseval.annotation.DoubleRange";
	/** org.hibernate.validator.constraints.Email */
	static final String AN_EMAIL = "@org.hibernate.validator.constraints.Email";
	/** org.hibernate.validator.constraints.CreditCardNumber */
	static final String AN_CREDIT_CARD = "@org.hibernate.validator.constraints.CreditCardNumber";
	/** javax.validation.constraints.Size */
	static final String AN_LENGTH = "@javax.validation.constraints.Size";
	/** org.apache.myfaces.extensions.validator.baseval.annotation.Pattern */
	static final String AN_PATTERN = "@org.apache.myfaces.extensions.validator.baseval.annotation.Pattern";
	/** org.apache.myfaces.extensions.validator.crossval.annotation.Equals */
	static final String AN_EQUALS = "@org.apache.myfaces.extensions.validator.crossval.annotation.Equals";

	/**
	 * @return the annotations
	 * @see org.andromda.cartridges.jsf.metafacades.JSFParameter#getMaxLength()
	 */
	@Override
	protected Collection<String> handleGetAnnotations() {
		final Collection<String> result = new HashSet<String>();
		boolean requiredAdded = false;
		for (String vt : (Collection<String>) getValidatorTypes()) {
			if (vt.startsWith("@")) // add the annotation
			{
				result.add(vt);
			}
			if (JSFUtils.VT_REQUIRED.equals(vt)) {
				requiredAdded = true;
				result.add(AN_REQUIRED);
			} else if (JSFUtils.VT_URL.equals(vt)) {
				result.add(AN_URL);
			} else if (JSFUtils.VT_INT_RANGE.equals(vt)) {
				final StringBuilder sb = new StringBuilder(AN_LONG_RANGE + "(");
				final String format = JSFUtils
						.getInputFormat((ModelElementFacade) this.THIS());
				final String rangeStart = JSFUtils.getRangeStart(format);
				boolean addComma = false;
				if (StringUtils.isNotBlank(rangeStart)
						&& !rangeStart.equals(UNDEFINED_BOUND)) {
					sb.append("minimum=" + rangeStart);
					addComma = true;
				}
				final String rangeEnd = JSFUtils.getRangeEnd(format);
				if (StringUtils.isNotBlank(rangeEnd)
						&& !rangeEnd.equals(UNDEFINED_BOUND)) {
					if (addComma) {
						sb.append(",");
					}
					sb.append("maximum=" + rangeEnd);
				}
				sb.append(")");
				result.add(sb.toString());
			} else if (JSFUtils.VT_FLOAT_RANGE.equals(vt)
					|| JSFUtils.VT_DOUBLE_RANGE.equals(vt)) {
				final StringBuilder sb = new StringBuilder(AN_DOUBLE_RANGE
						+ "(");
				final String format = JSFUtils
						.getInputFormat(((ModelElementFacade) this.THIS()));
				final String rangeStart = JSFUtils.getRangeStart(format);
				boolean addComma = false;
				if (StringUtils.isNotBlank(rangeStart)
						&& !rangeStart.equals(UNDEFINED_BOUND)) {
					sb.append("minimum=" + rangeStart);
					addComma = true;
				}
				final String rangeEnd = JSFUtils.getRangeEnd(format);
				if (StringUtils.isNotBlank(rangeEnd)
						&& !rangeEnd.equals(UNDEFINED_BOUND)) {
					if (addComma) {
						sb.append(",");
					}
					sb.append("maximum=" + rangeEnd);
				}
				sb.append(")");
				result.add(sb.toString());
			} else if (JSFUtils.VT_EMAIL.equals(vt)) {
				result.add(AN_EMAIL);
			} else if (JSFUtils.VT_CREDIT_CARD.equals(vt)) {
				result.add(AN_CREDIT_CARD);
			} else if (JSFUtils.VT_MIN_LENGTH.equals(vt)
					|| JSFUtils.VT_MAX_LENGTH.equals(vt)) {
				final StringBuilder sb = new StringBuilder(AN_LENGTH + "(");
				final Collection formats = this
						.findTaggedValues(JSFProfile.TAGGEDVALUE_INPUT_FORMAT);
				boolean addComma = false;
				for (final Iterator formatIterator = formats.iterator(); formatIterator
						.hasNext();) {
					final String additionalFormat = String
							.valueOf(formatIterator.next());
					if (JSFUtils.isMinLengthFormat(additionalFormat)) {
						if (addComma) {
							sb.append(",");
						}
						sb.append("min=");
						sb.append(JSFUtils.getMinLengthValue(additionalFormat));
						addComma = true;
					} else if (JSFUtils.isMaxLengthFormat(additionalFormat)) {
						if (addComma) {
							sb.append(",");
						}
						sb.append("max=");
						sb.append(JSFUtils.getMinLengthValue(additionalFormat));
						addComma = true;
					}
				}
				sb.append(")");
				result.add(sb.toString());
			} else if (JSFUtils.VT_MASK.equals(vt)) {
				final Collection formats = this
						.findTaggedValues(JSFProfile.TAGGEDVALUE_INPUT_FORMAT);
				for (final Iterator formatIterator = formats.iterator(); formatIterator
						.hasNext();) {
					final String additionalFormat = String
							.valueOf(formatIterator.next());
					if (JSFUtils.isPatternFormat(additionalFormat)) {
						result.add(AN_PATTERN + "(\""
								+ JSFUtils.getPatternValue(additionalFormat)
								+ "\")");
					}
				}
			} else if (JSFUtils.VT_VALID_WHEN.equals(vt)) {
				result.add("");
			} else if (JSFUtils.VT_EQUAL.equals(vt)) {
				result.add(AN_EQUALS + "(\""
						+ JSFUtils.getEqual((ModelElementFacade) this.THIS())
						+ "\")");
			}
		}
		if (!requiredAdded && getLower() > 0) {
			result.add(AN_REQUIRED);
		}
		return result;
	}

	@Override
	protected String handleGetZone() {
		String zone = (String) this
				.findTaggedValue(JSFProfile.TAGGEDVALUE_ZONE);
		if (StringUtils.isNotBlank(zone)) {
			return zone;
		}
		return "default";
	}

}